Exploring Lower-Bounded Wildcards and Unbounded Wildcards in Java
Course: Core Java Training
Topic: Exploring Lower-Bounded Wildcards and Unbounded Wildcards
Course Link: Core Java Training Course
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to Wildcards in Java Generics
Java Generics allow classes, interfaces, and methods to work with different data types while providing compile-time type safety. A wildcard is represented by the question mark (?) and represents an unknown type. Wildcards are especially useful when a method needs to accept collections whose element types are related but not exactly the same.
Java supports three common wildcard forms:
- Unbounded wildcard:
List<?> — a list whose element type is unknown.
- Upper-bounded wildcard:
List<? extends Number> — a list of an unknown type that is Number or a subtype of Number.
- Lower-bounded wildcard:
List<? super Integer> — a list of an unknown type that is Integer or a supertype of Integer.
This topic focuses on unbounded and lower-bounded wildcards, which help developers design flexible methods for handling collections of different types.
2. Learning Objectives
- Understand what wildcards represent in Java Generics.
- Explain the syntax and purpose of unbounded wildcards.
- Use lower-bounded wildcards with the
super keyword.
- Understand which operations are safe with each wildcard type.
- Differentiate
List<Object> from List<?>.
- Apply wildcards to collection-processing methods.
- Understand the PECS guideline for choosing wildcard bounds.
3. What Is an Unbounded Wildcard?
An unbounded wildcard uses the question mark without an explicit upper or lower bound. For example:
List<?> items;
This declaration means that items refers to a list of some unknown element type. The list could contain strings, integers, doubles, or objects of another reference type.
For example, all the following lists can be passed to a method that accepts List<?>:
List<String> names;
List<Integer> numbers;
List<Double> prices;
The wildcard does not mean that the list can freely accept every possible type. It means that the element type is unknown to the code using that reference.
4. Why Use Unbounded Wildcards?
Suppose a method only needs to print the contents of a list. It does not need to know whether the list contains strings, integers, or another type. Using List<?> allows the method to accept lists of many different element types.
Example: Printing a list
import java.util.Arrays;
import java.util.List;
public class UnboundedWildcardExample {
public static void printList(List<?> items) {
for (Object item : items) {
System.out.println(item);
}
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Amit", "Neha");
List<Integer> numbers = Arrays.asList(10, 20, 30);
printList(names);
printList(numbers);
}
}
Output:
Amit
Neha
10
20
30
Explanation:
printList() accepts a list with any element type.
- Each element can safely be read as an
Object, because every Java reference type is derived from Object.
- The method does not need to declare a named type parameter such as
<T> because it does not need to link the element type to another parameter or return value.
5. Understanding List<?> and List<Object>
These declarations look similar, but they have different meanings and are not interchangeable.
| Feature |
List<Object> |
List<?> |
| Meaning |
A list whose element type is specifically Object. |
A list whose element type is unknown. |
Accepts List<String> directly? |
No. |
Yes. |
Accepts List<Integer> directly? |
No. |
Yes. |
| Can add a string? |
Yes, because a string is an Object. |
No, because the actual element type is unknown. |
Can read an element as Object? |
Yes. |
Yes. |
| Typical purpose |
Store arbitrary objects in a list specifically typed as Object. |
Read or inspect collections of unknown element type. |
Example showing the difference
import java.util.ArrayList;
import java.util.List;
public class ListTypeDifference {
public static void main(String[] args) {
List<Object> objectList = new ArrayList<>();
objectList.add("Java");
objectList.add(100);
List<String> stringList = new ArrayList<>();
stringList.add("Generics");
List<?> unknownList = stringList;
System.out.println(objectList);
System.out.println(unknownList);
// This is not allowed:
// objectList = stringList;
// This is also not allowed:
// unknownList.add("New value");
}
}
Output:
[Java, 100]
[Generics]
Key point: Java generics are invariant. Even though String is a subtype of Object, List<String> is not a subtype of List<Object>. A wildcard such as List<?> provides a way to accept lists of unknown element types safely.
6. What Can You Do with an Unbounded Wildcard?
Since the element type is unknown, Java restricts operations that could insert an incompatible object.
Allowed operations
- Read an element and store it in an
Object variable.
- Check the list's size using
size().
- Check whether it is empty using
isEmpty().
- Iterate through its elements.
- Clear the list using
clear(), if the underlying list supports modification.
- Remove elements through supported collection operations.
- Add
null, because null can be assigned to any reference type, although the underlying collection may impose its own restrictions.
Operations that are not allowed
List<?> items = new ArrayList<String>();
// Compile-time errors:
// items.add("Java");
// items.add(100);
Object value = items.get(0);
The compiler cannot determine whether items is a list of strings, integers, or another type. Therefore, it cannot guarantee that adding a string or integer would be safe. Reading as Object is safe because every element is an object reference.
7. What Is a Lower-Bounded Wildcard?
A lower-bounded wildcard uses the super keyword. Its general syntax is:
<? super Type>
For example:
List<? super Integer>
This means that the list's actual element type is Integer or a supertype of Integer, such as Number or Object.
Consider these valid declarations:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
List<? super Integer> first = integers;
List<? super Integer> second = numbers;
List<? super Integer> third = objects;
Each assignment is valid because the list can hold an Integer value according to its declared element type.
8. Why Use Lower-Bounded Wildcards?
Lower-bounded wildcards are especially useful when a method needs to insert values into a collection. If a method must add integers, requiring only List<Integer> would be unnecessarily restrictive. A parameter of type List<? super Integer> also accepts lists of Number and Object.
Example: Adding integers to a list
import java.util.ArrayList;
import java.util.List;
public class LowerBoundExample {
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
list.add(30);
}
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(integers);
addNumbers(numbers);
addNumbers(objects);
System.out.println(integers);
System.out.println(numbers);
System.out.println(objects);
}
}
Output:
[10, 20, 30]
[10, 20, 30]
[10, 20, 30]
Explanation:
- The method accepts lists whose element type is
Integer or a supertype of Integer.
- Adding an
Integer is safe for all accepted list types.
- The method cannot safely add an arbitrary
Number or an arbitrary Object, because the actual list might be a List<Integer>.
9. Reading Values from a Lower-Bounded Wildcard
A lower-bounded wildcard is useful for adding values, but it provides less information when reading elements. For a variable declared as List<? super Integer>, the compiler does not know whether the list is a List<Integer>, a List<Number>, or a List<Object>.
List<? super Integer> values = new ArrayList<Number>();
values.add(42);
Object item = values.get(0);
// This does not compile:
// Integer number = values.get(0);
System.out.println(item);
Output:
42
The result of get() can safely be assigned to an Object. It cannot generally be assigned directly to an Integer, because the list's declared element type may be Number or Object.
10. Unbounded vs. Lower-Bounded Wildcards
| Feature |
Unbounded Wildcard |
Lower-Bounded Wildcard |
| Syntax |
<?> |
<? super Integer> |
| Meaning |
Unknown element type. |
Unknown element type that is Integer or a supertype. |
| Primary purpose |
Read or inspect values without needing their exact type. |
Insert values of the specified type safely. |
| Can add an integer? |
No, except for null. |
Yes, when the lower bound is Integer. |
Can read as Object? |
Yes. |
Yes. |
| Typical example |
Printing arbitrary lists. |
Adding integers to lists of integers or their supertypes. |
11. Choosing the Correct Wildcard: PECS
PECS stands for Producer Extends, Consumer Super. It is a common guideline for deciding which wildcard to use in collection APIs.
- Producer extends: Use
? extends T when a collection provides values to your code and you mainly need to read them as type T.
- Consumer super: Use
? super T when your code supplies values of type T to a collection.
- Unbounded wildcard: Use
? when the exact type is irrelevant and you only need operations that do not depend on the element type.
For example, a method that copies elements from a source list into a destination list can use an upper-bounded source and a lower-bounded destination.
import java.util.List;
public class CopyExample {
public static <T> void copy(
List<? extends T> source,
List<? super T> destination) {
for (T item : source) {
destination.add(item);
}
}
public static void main(String[] args) {
List<Integer> source = List.of(1, 2, 3);
List<Number> destination =
new java.util.ArrayList<>();
copy(source, destination);
System.out.println(destination);
}
}
Output:
[1, 2, 3]
In this example, the source produces values that can be treated as T, while the destination consumes values of type T. This is a practical application of PECS.
12. Common Mistakes and How to Fix Them
| Mistake |
Why It Is Incorrect |
Correct Approach |
Adding a string to List<?> |
The actual element type is unknown. |
Use a suitably typed list or a lower-bounded wildcard when insertion is required. |
Assigning List<String> to List<Object> |
Generic types are invariant. |
Use List<?> for an unknown list type or choose an appropriate bound. |
Adding any object to List<? super Integer> |
The actual list may be a List<Integer>. |
Add only Integer values or values compatible with the lower bound. |
Reading from a lower-bounded list directly into an Integer |
The actual element type could be Number or Object. |
Read into an Object variable unless a safe, justified conversion is available. |
| Using a wildcard when a named type is needed |
A wildcard does not provide a reusable type name. |
Use a generic type parameter such as <T> when the same type must link multiple arguments or a return value. |
13. Practical Applications
Application 1: Printing collection values
Use List<?> when a utility method only needs to display or inspect elements without changing their values.
Application 2: Adding values to collections
Use a lower-bounded wildcard such as List<? super Integer> when a method needs to insert integers into lists of integers or their supertypes.
Application 3: Reusable collection utilities
Methods that copy, transfer, or populate data can combine upper- and lower-bounded wildcards to support a broad range of compatible collection types.
Application 4: Working with APIs
Wildcards are widely used in Java collection APIs because they allow methods to accept compatible generic types without sacrificing compile-time type safety.
14. Practice Exercises
- Create a method that accepts
List<?> and prints each element.
- Write a method that accepts
List<? super String> and adds three strings to the list.
- Create an integer list, a number list, and an object list. Pass each to a method that accepts
List<? super Integer>.
- Attempt to add an integer to
List<?> and explain the compiler error.
- Write a generic copy method using
List<? extends T> as the source and List<? super T> as the destination.
- Explain why
List<String> cannot be assigned to List<Object>, but can be assigned to List<?>.
15. Mini Project: Generic List Utility
Objective: Create a utility class that can inspect lists of any element type and insert specified values into compatible destination lists.
Requirements:
- Implement a method called
displayItems(List<?> items).
- Implement a method called
addDefaultNames(List<? super String> destination).
- Test the methods with lists of strings, integers, numbers, and objects where appropriate.
- Print the resulting contents and observe which assignments compile.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GenericListUtility {
public static void displayItems(List<?> items) {
for (Object item : items) {
System.out.println(item);
}
}
public static void addDefaultNames(
List<? super String> destination) {
destination.add("Java");
destination.add("Generics");
destination.add("Wildcards");
}
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(10, 20, 30);
displayItems(numbers);
List<Object> values = new ArrayList<>();
addDefaultNames(values);
displayItems(values);
}
}
Expected output:
10
20
30
Java
Generics
Wildcards
This project demonstrates how unbounded wildcards support general inspection and lower-bounded wildcards support safe insertion into compatible collections.
16. Interview Questions and Answers
Q1. What is an unbounded wildcard in Java?
An unbounded wildcard is represented by ?, as in List<?>. It represents a collection whose element type is unknown.
Q2. What is a lower-bounded wildcard?
A lower-bounded wildcard uses super, as in List<? super Integer>. It accepts a list whose element type is Integer or a supertype of Integer.
Q3. Why can values not generally be added to List<?>?
The actual element type is unknown. Adding an arbitrary value could violate the list's element type, so the compiler prevents it. The exception is null, subject to the underlying collection's restrictions.
Q4. Why can an integer be added to List<? super Integer>?
Every permitted element type is Integer or a supertype of it, so an integer is compatible with all accepted list types.
Q5. What is the difference between List<Object> and List<?>?
List<Object> is specifically a list of objects, while List<?> can refer to a list with any element type. A List<String> can be passed to a method accepting List<?>, but not to one accepting List<Object>.
Q6. What does PECS mean?
PECS means Producer Extends, Consumer Super. Use extends when values are primarily produced for reading, and super when values are consumed by a destination.
Q7. Can a lower-bounded wildcard be used with an interface?
Yes. For example, List<? super Runnable> can refer to a list whose element type is Runnable or a supertype of that interface, such as Object.
Q8. Can a wildcard have both an upper and lower bound simultaneously?
No. A wildcard can specify an upper bound with extends or a lower bound with super, but not both in the same wildcard expression.
17. Quick Revision Checklist
? represents an unknown type.
List<?> can accept lists with different element types.
- Values read from
List<?> can safely be treated as Object.
List<? super Integer> accepts lists of Integer and its supertypes.
- Lower-bounded wildcards are useful when inserting values into a collection.
List<Object> and List<?> are different types.
- PECS helps select the correct wildcard bound.
- Wildcards provide flexibility while preserving compile-time type safety.
18. Conclusion
Unbounded and lower-bounded wildcards are important tools for writing flexible Java code with Generics. An unbounded wildcard, such as List<?>, is useful when the exact element type is irrelevant and the code needs to inspect or process a collection. A lower-bounded wildcard, such as List<? super Integer>, is useful when the code needs to add values of a particular type to a collection that can safely hold them.
Understanding these wildcard forms, their restrictions, and the PECS guideline helps developers build reusable collection utilities and work confidently with Java's type system.
Continue learning: Explore the Core Java Training course, register for a course demo, or visit the JustAcademy website.